package pacs_server

import (
	"bytes"
	"errors"
	"fmt"
	"gitee.com/uni-minds/bridge-pacs/custom"
	"gitee.com/uni-minds/bridge-pacs/dcm4chee"
	"gitee.com/uni-minds/bridge-pacs/global"
	"gitee.com/uni-minds/bridge-pacs/tools"
	media "gitee.com/uni-minds/utils/media"
	"image/png"
	"io/ioutil"
	"net/http"
	"os"
	"path"
)

func (pi *PacsServer) checkInstanceMedia(info global.InstanceInfo) (err error) {
	switch info.MediaType {
	case dcm4chee.MEDIA_TYPE_IMAGE:
		info.Duration = 0
		return pi.InstanceUpdateAll(info.InstanceId, info)

	case dcm4chee.MEDIA_TYPE_MULTI_FRAME:
		cache := tools.DecodeCachePathVideo(info.CacheLocalPath)
		ogvPath := path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)
		if _, err = os.Stat(ogvPath); err == nil {
			var duration float64
			if duration, err = tools.MediaGetDuration(ogvPath); err == nil {
				info.Duration = duration
				err = pi.InstanceUpdateAll(info.InstanceId, info)
			}
		}
		return err

	default:
		pi.Log("unknown media type:", info.MediaType)
		return errors.New(fmt.Sprintf("unknown media type: %s", info.MediaType))
	}
}

// InstanceMediaCheck targetType=image | thumb | video/
func (pi *PacsServer) InstanceMediaCheck(id string, targetType string) error {
	info, err := pi.FindInstanceByIdLocal(id)
	if err != nil {
		return err
	}

	var filename string

	switch info.MediaType {
	case dcm4chee.MEDIA_TYPE_IMAGE:
		cache := tools.DecodeCachePathImage(info.CacheLocalPath)

		filelock := path.Join(pi.CacheRoot, cache.Path, LOCKFILE)

		if _, err = os.Stat(filelock); err == nil {
			return custom.ErrInstanceMediaIsConverting()
		}

		switch targetType {
		case "thumb":
			filename = cache.ThumbFilename

		case "image":
			filename = cache.ImageFilename

		case "video":
			filename = cache.VideoFilename

		case "all":
			if cache.ThumbFilename == "" || cache.VideoFilename == "" || cache.ImageFilename == "" {
				return custom.ErrInstanceMediaIsNotExist()
			} else if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, cache.ThumbFilename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			} else if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			} else if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, cache.ImageFilename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			} else {
				return nil
			}

		default:
			return errors.New("unknown target type")
		}

		switch filename {
		case "":
			return custom.ErrInstanceMediaIsNotExist()

		default:
			if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, filename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			}
		}
		return nil

	case dcm4chee.MEDIA_TYPE_MULTI_FRAME:
		cache := tools.DecodeCachePathVideo(info.CacheLocalPath)

		if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, LOCKFILE)); err == nil {
			return custom.ErrInstanceMediaIsConverting()
		}

		switch targetType {
		case "thumb":
			filename = cache.VideoFilename

		case "video":
			filename = cache.VideoFilename

		case "all":
			if cache.ThumbFilename == "" || cache.VideoFilename == "" {
				return custom.ErrInstanceMediaIsNotExist()
			} else if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, cache.ThumbFilename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			} else if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			} else {
				return nil
			}

		default:
			return errors.New("unknown target type")
		}

		switch filename {
		case "":
			return custom.ErrInstanceMediaIsNotExist()

		default:
			if _, err = os.Stat(path.Join(pi.CacheRoot, cache.Path, filename)); err != nil {
				return custom.ErrInstanceMediaIsNotExist()
			}
		}
		return nil
	default:
		return errors.New(fmt.Sprintf("unknown media type: %s", info.MediaType))

	}
}
func (pi *PacsServer) InstanceMediaGet(id string, targetType string) (bs []byte, mediaType string, err error) {
	info, err := pi.FindInstanceByIdLocal(id)
	if err != nil {
		return nil, "", err
	} else if err = pi.InstanceMediaCheck(id, "all"); err != nil {
		if info, err = pi.InstanceMediaGenerate(id, true); err != nil {
			return nil, "", err
		}
	}

	switch info.MediaType {
	case dcm4chee.MEDIA_TYPE_IMAGE:
		cache := tools.DecodeCachePathImage(info.CacheLocalPath)
		if info.Duration < 0 {
			info.Duration = 0
			if err = pi.InstanceUpdateAll(id, info); err != nil {
				pi.Log("Error", err.Error())
			}
		}

		switch targetType {
		case "ogv":
			ogvFullpath := path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)
			bs, err = os.ReadFile(ogvFullpath)
			return bs, "ogv", err

		case "mp4":
			ogvFullpath := path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)
			target := ogvFullpath + ".mp4"
			stat, err := os.Stat(target)
			if os.IsNotExist(err) || stat.Size() == 0 {
				err = media.ConvertFormat(media.MediaInfo{
					Filepath: ogvFullpath,
				}, media.MediaInfo{
					Filepath:  target,
					MediaType: media.MP4V,
				})
				if err != nil {
					return nil, "", err
				}
			}
			bs, err = os.ReadFile(target)
			return bs, "video", nil

		default:
			bs, err = os.ReadFile(path.Join(pi.CacheRoot, cache.Path, cache.ImageFilename))
			return bs, "image", err

		}

	case dcm4chee.MEDIA_TYPE_MULTI_FRAME:
		cache := tools.DecodeCachePathVideo(info.CacheLocalPath)

		ogvFullpath := path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)
		if info.Duration <= 0 {
			durations, err := tools.MediaGetDuration(ogvFullpath)
			if err == nil && durations != info.Duration {
				info.Duration = durations
				err = pi.InstanceUpdateAll(id, info)
				if err == nil {
					pi.Log("instance multi frame duration update:", durations)
				}
			}
		}

		switch targetType {
		case "mp4":
			target := ogvFullpath + ".mp4"
			stat, err := os.Stat(target)
			if os.IsNotExist(err) || stat.Size() == 0 {
				err = media.ConvertFormat(media.MediaInfo{
					Filepath: ogvFullpath,
				}, media.MediaInfo{
					Filepath:  target,
					MediaType: media.MP4V,
				})
				if err != nil {
					return nil, "", err
				}
			}
			bs, err = os.ReadFile(target)
			return bs, "video", nil

		default:
			bs, err = os.ReadFile(ogvFullpath)
			return bs, "video", nil

		}

	default:
		return nil, "", errors.New(fmt.Sprintf("unknown type: %s", info.MediaType))

	}
}
func (pi *PacsServer) InstanceMediaGetThumb(id string) (bs []byte, err error) {
	info, err := pi.FindInstanceByIdLocal(id)
	if err != nil {
		return nil, err
	} else if err = pi.InstanceMediaCheck(id, "all"); err != nil {
		if info, err = pi.InstanceMediaGenerate(id, true); err != nil {
			return nil, err
		}
	}

	switch info.MediaType {
	case dcm4chee.MEDIA_TYPE_IMAGE:
		cache := tools.DecodeCachePathImage(info.CacheLocalPath)
		fn := path.Join(pi.CacheRoot, cache.Path, cache.ThumbFilename)
		return os.ReadFile(fn)

	case dcm4chee.MEDIA_TYPE_MULTI_FRAME:
		cache := tools.DecodeCachePathVideo(info.CacheLocalPath)
		fn := path.Join(pi.CacheRoot, cache.Path, cache.ThumbFilename)
		return os.ReadFile(fn)

	default:
		return nil, errors.New(fmt.Sprintf("unknown type: %s", info.MediaType))

	}
}
func (pi *PacsServer) InstanceMediaGetFrame(id string, frame int, imageType string) (bs []byte, err error) {
	var info global.InstanceInfo
	var u string

	if info, err = pi.FindInstanceByIdLocal(id); err != nil {
		return nil, err
	}

	switch info.MediaType {
	case dcm4chee.MEDIA_TYPE_MULTI_FRAME:
		pi.Log("mf")
		if frame < 0 {
			u = fmt.Sprintf("http://%s/dcm4chee-arc/aets/%s/rs/studies/%s/series/%s/instances/%s/rendered", pi.ServerHost, pi.ServerAETs, info.StudiesId, info.SeriesId, info.InstanceId)
			imageType = "gif"
		} else if frame <= info.Frames {
			u = fmt.Sprintf("http://%s/dcm4chee-arc/aets/%s/rs/studies/%s/series/%s/instances/%s/frames/%d/rendered", pi.ServerHost, pi.ServerAETs, info.StudiesId, info.SeriesId, info.InstanceId, frame+1)
		} else {
			return nil, errors.New("wrong frame")
		}

	case dcm4chee.MEDIA_TYPE_IMAGE:
		pi.Log("im")
		if frame > 0 {
			return nil, errors.New("wrong frame")
		} else {
			u = fmt.Sprintf("http://%s/dcm4chee-arc/aets/%s/rs/studies/%s/series/%s/instances/%s/rendered", pi.ServerHost, pi.ServerAETs, info.StudiesId, info.SeriesId, info.InstanceId)
		}

	default:
		return nil, errors.New("unsupported format")
	}

	client := &http.Client{}

	req, err := http.NewRequest("GET", u, nil)
	if err != nil {
		return nil, err
	}

	switch imageType {
	case "png":
		req.Header.Set("Accept", "image/png")
	case "jpg", "jpeg":
		req.Header.Set("Accept", "image/jpeg")
	case "gif":
		req.Header.Set("Accept", "image/gif")
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	} else {
		bs, err = ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		return bs, err
	}
}
func (pi *PacsServer) InstanceMediaGenerate(id string, force bool) (global.InstanceInfo, error) {
	info, err := pi.FindInstanceByIdLocal(id)
	if err != nil {
		return info, err
	}

	switch info.MediaType {
	case dcm4chee.MEDIA_TYPE_IMAGE:
		cache := tools.DecodeCachePathImage(info.CacheLocalPath)

		if cache.Path != "" {
			f := path.Join(pi.CacheRoot, cache.Path, LOCKFILE)
			if _, err = os.Stat(f); err == nil {
				return info, custom.ErrFolderIsLock()
			}
		}

		if cache.ImageFilename != "" {
			p := path.Join(pi.CacheRoot, cache.Path, cache.ImageFilename)
			_, err = os.Stat(p)
			if err == nil && !force {
				return info, errors.New("cache file exist, ignore")
			}
		}

		u := fmt.Sprintf("%s/rendered", info.URL)
		bs, err := tools.DownloadImage(u, "png")
		if err != nil {
			return info, err
		}

		instanceFolder := path.Join(info.StudiesId, info.SeriesId)
		instanceFolderFull := path.Join(pi.CacheRoot, instanceFolder)
		fileLock := path.Join(instanceFolderFull, LOCKFILE)

		if _, err = os.Stat(instanceFolderFull); err != nil {
			os.MkdirAll(instanceFolderFull, os.ModePerm)
		}

		fnImage := fmt.Sprintf("%s.png", info.InstanceId)
		fnThumb := fmt.Sprintf("%s_thumb_300.jpg", info.InstanceId)
		fnVideo := fmt.Sprintf("%s.ogv", info.InstanceId)

		if fp, err := os.Create(fileLock); err != nil {
			return info, err
		} else {
			pi.Log("lock create:", fileLock)
			fp.Close()
		}

		defer func() {
			pi.Log("lock remove:", fileLock)
			os.Remove(fileLock)
		}()

		if err = tools.SaveToFile(path.Join(instanceFolderFull, fnImage), bs); err != nil {
			return info, err
		} else if img, err := png.Decode(bytes.NewReader(bs)); err != nil {
			pi.Log("E png decode:", err.Error())
		} else if err = tools.ImageGenerateThumbnail(path.Join(instanceFolderFull, fnThumb), img, 300); err != nil {
			pi.Log("E thumbnail:", err.Error())
		} else if err = tools.GenerateVideoFromImage(path.Join(instanceFolderFull, fnImage), path.Join(instanceFolderFull, fnVideo)); err != nil {
			pi.Log("E png->ogv:", err.Error())
		}

		cache = global.CachePathImage{
			Path:          instanceFolder,
			ImageFilename: fnImage,
			ThumbFilename: fnThumb,
			VideoFilename: fnVideo,
			GenDate:       tools.TimeEncodeNow(),
		}

		info.CacheLocalPath = tools.EncodeCachePathImage(cache)

	case dcm4chee.MEDIA_TYPE_MULTI_FRAME:
		cache := tools.DecodeCachePathVideo(info.CacheLocalPath)

		if cache.Path != "" {
			f := path.Join(pi.CacheRoot, cache.Path, LOCKFILE)
			if _, err = os.Stat(f); err == nil {
				return info, custom.ErrFolderIsLock()
			}
		}

		if cache.VideoFilename != "" {
			p := path.Join(pi.CacheRoot, cache.Path, cache.VideoFilename)
			_, err := os.Stat(p)
			if err == nil && !force {
				return info, errors.New("cache file exist, ignore")
			}
		}

		instanceFolder := path.Join(info.StudiesId, info.SeriesId)
		instanceFolderFull := path.Join(pi.CacheRoot, instanceFolder)
		if _, err := os.Stat(instanceFolderFull); err != nil {
			os.MkdirAll(instanceFolderFull, os.ModePerm)
		}

		instanceFrameFn := make([]string, 0)
		fnThumb := fmt.Sprintf("%s_thumb_300.jpg", info.InstanceId)
		fnVideo := fmt.Sprintf("%s.ogv", info.InstanceId)
		fileLock := path.Join(instanceFolderFull, LOCKFILE)

		pi.Logf("||- instance has %d frames\n", info.Frames)

		if fp, err := os.Create(fileLock); err != nil {
			return info, err
		} else {
			pi.Log("lock create:", fileLock)
			fp.Close()
		}

		defer func() {
			pi.Log("lock remove:", fileLock)
			os.Remove(fileLock)
		}()

		pi.Logf("converting")
		for i := 1; i <= info.Frames; i++ {
			fnFrame := fmt.Sprintf("%s_f%d.png", info.InstanceId, i)
			u := fmt.Sprintf("%s/frames/%d/rendered", info.URL, i)
			bs, err := tools.DownloadImage(u, "png")
			if err != nil {
				return info, err
			}

			if err = tools.SaveToFile(path.Join(instanceFolderFull, fnFrame), bs); err != nil {
				return info, err
			}
			instanceFrameFn = append(instanceFrameFn, fnFrame)

			//GenThumb
			if i == 1 {
				if img, err := png.Decode(bytes.NewReader(bs)); err != nil {
					pi.Log("E png decode:", err.Error())
				} else if err = tools.ImageGenerateThumbnail(path.Join(instanceFolderFull, fnThumb), img, 300); err != nil {
					pi.Log("E thumbnail:", err.Error())
				}
			}

			if i%5 == 1 {
				fmt.Printf(".")
			}
		}
		pi.Log("OK")

		if err := tools.GenerateVideoFromFrames(instanceFolderFull, info.InstanceId, fnVideo); err != nil {
			return info, err
		}

		cache = global.CachePathVideo{
			Path:          instanceFolder,
			VideoFilename: fnVideo,
			ThumbFilename: fnThumb,
			FrameFilename: instanceFrameFn,
			GenDate:       tools.TimeEncodeNow(),
		}

		info.CacheLocalPath = tools.EncodeCachePathVideo(cache)
	}

	err = pi.InstanceUpdateAll(id, info)
	return info, err
}
